namespace Nico {
    public class Server : GLib.Object {
        private GLib.Socket socket;
        private List<Session> sessions;
        private int session_id = 1;
        private List<string> locations;
        private List<ConnectionHandler> connections;

        public Server() {
            this.locations = new List<string>();
            Json.Parser parser = new Json.Parser();
            try {
                parser.load_from_file("../data/config.json");
                Json.Array locations = parser.get_root().get_object().get_member("locations").get_array();
                locations.foreach_element((_,i,e) => {
                    message(e.get_string());
                    this.locations.append(e.get_string());
                });
            } catch (Error e) {
                warning(e.message);
            }

            try {
                this.socket = new Socket(SocketFamily.IPV4, SocketType.STREAM, SocketProtocol.TCP);
                var address = new InetAddress.any(SocketFamily.IPV4);
                var inetaddress = new InetSocketAddress (address, 8477);

                this.socket.bind (inetaddress, true);
                this.socket.set_listen_backlog (10);
                this.socket.listen ();
            } catch (Error e) {
                warning(e.message);
            }

            while (true) {
                Socket connection = socket.accept();
                var ra = connection.get_remote_address();
                if (this.is_connected_to(ra)) {
                    message("Dropped connection from %s. Double connection", ra.to_string());
                    connection.close();
                    continue;
                }
                this.connections.append(new ConnectionHandler(this, connection));
            }
        }

        private bool is_connected_to(GLib.SocketConnectable address) {
            foreach (var connection in this.connections) {
                if (connection.is_from(address))
                    return true;
            }
            return false;
        }
    
        public void add_session(Session s) {
            lock (this.sessions) {
                s.id = this.session_id++;
                this.sessions.append(s);
            }
        }

        private Session? get_session(int session_id) {
            foreach (var s in this.sessions) {
                if (s.id == session_id)
                    return s; 
            }
            return null;
        }

        public void broadcast_update(int session_id) {
            var s = this.get_session(session_id);
            if (s != null) {
                foreach (var ch in this.connections) {
                    ch.send_update(s);
                }
            }
        }

        public void refuse_session(int session_id, string username) {
            var s = this.get_session(session_id);
            if (s == null) {
                warning("invalid session id: %d", session_id);
                return;
            }
            s.remove_user(username);
        }

        public void join_session(int session_id, Resources resources, string username) {
            var s = this.get_session(session_id);
            if (s == null) {
                warning("invalid session id: %d", session_id);
                return;
            }
            if (s.add_user(username)) {
                s.resources.tabak |= resources.tabak;
                s.resources.filter |= resources.filter;
                s.resources.papes |= resources.papes;
                s.resources.lighter |= resources.lighter;
            }
        }

        public string[] get_locations () {
            var locs = new string[this.locations.length()];
            uint i = 0;
            foreach (string loc in this.locations) {
                locs[i++] = loc;
            }
            return locs;
        }

        public unowned List<Session> get_sessions() {
            lock (this.sessions) {
                return this.sessions;
            }
        }
    }
}
